package org.syncsoft.overalt.core.util;

import java.io.File;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.syncsoft.overalt.common.api.Configuration;
import org.syncsoft.overalt.common.exception.SystemException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * class that manipulates a XML file
 * it implements the singleton pattern
 * 
 * @author eajf 
 *
 */
public class XMLCore {

	private static XMLCore instance;

	public static XMLCore getInstance(){
		if(instance == null){
			instance = new XMLCore();
		}
		return instance;
	}

	private XMLCore(){

	}

	/**
	 * Insert a folder tag on XML.
	 * 
	 * @param folderFilepath The Folder path.
	 * @throws SystemException
	 */
	public void insertFolder(String folderFilepath)
	throws SystemException {
		Document doc = getXML();
		Element root = doc.getDocumentElement();
		Element elementConfig = doc.createElement("config");
		Element elementFolder = doc.createElement("folder");
		elementFolder.setAttribute("path", folderFilepath);
		elementFolder.appendChild(elementConfig);
		root.appendChild(elementFolder);
		setXML(doc);
	}

	/**
	 * Insert a File tag into a folder tag on XML.
	 * 
	 * @param folderFilepath - The Folder path
	 * @param filepath - The file path
	 * @param fileID - The file Id
	 * @param commitable - If the file is commitable or not.
	 * @throws SystemException
	 */
	public void insertFile(String folderFilepath, String filepath, String fileID, boolean commitable)
	throws SystemException {
		Document doc = getXML();
		Element element = doc.createElement("file");
		element.setAttribute("path", filepath);
		element.setAttribute("id", fileID);
		element.setAttribute("commitable", String.valueOf(commitable));
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folder = getFolder(folderFilepath, nodes);
		if(folder != null){
			folder.appendChild(element);
			setXML(doc);
		}else{
			throw new SystemException("The folder "+folderFilepath+" doesn't exist.");
		}
	}

	/**
	 * Insert a File tag into a folder tag on XML.
	 * 
	 * @param folderFilepath - The folder path
	 * @param filepath - The file path
	 * @param fileID - The file Id
	 * @param commitable - If the file is commitable or not
	 * @param version - The file Version
	 * @throws SystemException
	 */
	public void insertFile(String folderFilepath, String filepath, String fileID, boolean commitable, String version)
	throws SystemException {
		Document doc = getXML();
		Element element = doc.createElement("file");
		element.setAttribute("path", filepath);
		element.setAttribute("id", fileID);
		element.setAttribute("commitable", String.valueOf(commitable));
		element.setAttribute("version", version);
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folder = getFolder(folderFilepath, nodes);
		if(folder != null){
			folder.appendChild(element);
			setXML(doc);
		}else{
			throw new SystemException("The folder "+folderFilepath+" doesn't exist.");
		}
	}

	/**
	 * Update a folder changing its path on XML
	 * 
	 * @param oldFolderFilepath - The old folder path
	 * @param newFolderFilepath - The new folder path
	 * @throws SystemException
	 */
	public void updateFolder(String oldFolderFilepath, String newFolderFilepath) 
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folder = getFolder(oldFolderFilepath, nodes);
		if(folder != null){
			folder.setAttribute("path", newFolderFilepath);
			setXML(doc);
		}else{
			throw new SystemException("The folder "+oldFolderFilepath+" doesn't exist");
		}
	}

	/**
	 * Update a file changing its path by its Id on XML
	 * 
	 * @param fileID - The file Id
	 * @param newFilepath - The new file path
	 * @throws SystemException
	 */
	public void updateFileById(String fileID, String newFilepath)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("file");
		Element file = getFileById(fileID, nodes);
		if(file != null){
			file.setAttribute("path", newFilepath);
			setXML(doc);
		}else{
			throw new SystemException("The id file "+fileID+" doesn't exist");
		}
	}

	/**
	 * Update a file changing its path by the old file path on XML
	 * 
	 * @param OldFilepath - The old file path
	 * @param newFilepath - The new file path
	 * @throws SystemException
	 */
	public void updateFileByFilepath(String OldFilepath, String newFilepath)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("file");
		Element file = getFileByFilepath(OldFilepath, nodes);
		if(file != null){
			file.setAttribute("path", newFilepath);
			setXML(doc);
		}else{
			throw new SystemException("The file "+OldFilepath+" doesn't exist");
		}
	}

	/**
	 * Retrieve the file Id by its file path and folder parent path
	 * 
	 * @param folderFilepath - The folder parent path
	 * @param filepath - The file path
	 * @return the file Id
	 * @throws SystemException
	 */
	public int getFileId(String folderFilepath, String filepath) 
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		int fileId = -1;
		Element folderTemp = getFolder(folderFilepath, nodes);
		if (folderTemp != null) {
			NodeList files = folderTemp.getElementsByTagName("file");
			Element fileTemp = getFileByFilepath(filepath, files);
			if (fileTemp != null) {
				fileId = Integer.parseInt(fileTemp.getAttribute("id"));
				return fileId;
			}else{
				throw new SystemException("The file "+filepath+" doesn't exist");
			}
		}else{
			throw new SystemException("The folder "+folderFilepath+" doesn't exist");
		}
	}	

	/**
	 * Set the File version on the XML
	 * 
	 * @param folderFilepath - The folder parent path
	 * @param filepath - The file path
	 * @param version - The new file version.
	 * @throws SystemException
	 */
	public void setFileVersion(String folderFilepath, String filepath, String version) 
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		Element file = null;
		Element folderTemp = getFolder(folderFilepath, nodes);
		if (folderTemp != null) {
			NodeList files = folderTemp.getElementsByTagName("file");
			file = getFileByFilepath(filepath, files);
			if (file != null) {
				file.setAttribute("version", version);
				setXML(doc);
			} else {
				throw new SystemException("The file " + filepath
						+ " doesn't exist.");
			}
		}
	}	

	/**
	 * Retrieve the file version by its path and folder parent path
	 * 
	 * @param folderFilepath - The folder parent path
	 * @param filepath - The file path
	 * @return the file version
	 * @throws SystemException
	 */
	public int getFileVersion(String folderFilepath, String filepath) 
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		int fileVersion = -1;
		Element folderTemp = getFolder(folderFilepath, nodes);
		NodeList files = folderTemp.getElementsByTagName("file");
		Element fileTemp = getFileByFilepath(filepath, files);
		fileVersion = Integer.parseInt(fileTemp.getAttribute("version"));
		return fileVersion;
	}	

	/**
	 * Retrieve a collection of all files that are shared
	 * 
	 * @return Documents that are commitable.
	 * @throws SystemException
	 */
	public Collection<org.syncsoft.overalt.domain.document.entity.Document> getFilesShared()
	throws SystemException {
		Collection<org.syncsoft.overalt.domain.document.entity.Document> files = 
			new ArrayList<org.syncsoft.overalt.domain.document.entity.Document>();
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("file");
		for (int i = 0; i < nodes.getLength(); i++) {
			Element fileTemp = (Element) nodes.item(i);
			if (fileTemp != null) {
				org.syncsoft.overalt.domain.document.entity.Document document = new org.syncsoft.overalt.domain.document.entity.Document(
						fileTemp.getAttribute("path"), Boolean
								.parseBoolean(fileTemp
										.getAttribute("commitable")));
				files.add(document);
			}
		}
		return files;
	}

	/**
	 * Retrieve a collection of files that are shared by the folder parent path.
	 * 
	 * @param folderFilepath - The folder parent path
	 * @return Documents from the folder parent that are commitable
	 * @throws SystemException
	 */
	public Collection<org.syncsoft.overalt.domain.document.entity.Document> getFilesSharedByFolder(String folderFilepath)
	throws SystemException {
		Collection<org.syncsoft.overalt.domain.document.entity.Document> filesList = 
			new ArrayList<org.syncsoft.overalt.domain.document.entity.Document>();
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folderTemp = getFolder(folderFilepath, nodes);
		NodeList files = folderTemp.getElementsByTagName("file");
		for (int j = 0; j < files.getLength(); j++) {
			Element fileTemp = (Element) files.item(j);
			if (fileTemp != null) {
				org.syncsoft.overalt.domain.document.entity.Document document = new org.syncsoft.overalt.domain.document.entity.Document(
						fileTemp.getAttribute("path"), Boolean
								.parseBoolean(fileTemp
										.getAttribute("commitable")));
				filesList.add(document);
			}
		}

		return filesList;
	}

	/**
	 * Remove a folder tag and its children by the folder path.
	 * 
	 * @param folderFilepath - The folder path
	 * @throws SystemException
	 */
	public void removeFolder(String folderFilepath)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folder = getFolder(folderFilepath, nodes);
		if(folder != null){
			doc.getDocumentElement().removeChild(folder);
			setXML(doc);
		}else{
			throw new SystemException("The folder "+folderFilepath+" doesn't exist");
		}
	}

	/**
	 * Remove the file tag its children by the file path.
	 * 
	 * @param filepath - The file path
	 * @throws SystemException
	 */
	public void removeFileByFilepath(String filepath)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("file");
		Element file = getFileByFilepath(filepath, nodes);
		if(file != null){
			file.getParentNode().removeChild(file);
			setXML(doc);
		}else{
			throw new SystemException("The file "+filepath+" doesn't exist");
		}
	}

	/**
	 * Remove the file tag and its children by the file id
	 * 
	 * @param fileID - The file Id
	 * @throws SystemException
	 */
	public void removeFileByID(String fileID)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("file");
		Element file = getFileById(fileID, nodes);
		if(file != null){
			file.getParentNode().removeChild(file);
			setXML(doc);
		}else{
			throw new SystemException("The id file "+fileID+" doesn't exist");
		}
	}

	/**
	 * Retrieve the folder Element by its folder path
	 * 
	 * @param folderFilepath - The Folder path
	 * @param nodes - The node list containing all folders of the XML
	 * @return The Folder Element
	 * @throws SystemException
	 */
	protected Element getFolder(String folderFilepath, NodeList nodes)
	throws SystemException {
		Element folder = null;
		for (int i = 0; i < nodes.getLength(); i++) {
			Element temp = (Element) nodes.item(i);
			if(temp.getAttribute("path").equals(folderFilepath)){
				folder = temp;
				break;
			}
		}
		if(folder != null){
			return folder;
		}else{
			throw new SystemException("The folder "+folderFilepath+" doesn't exist.");
		}
	}

	/**
	 * Retrieve the file Element by its file Id
	 * 
	 * @param fileID - The file id
	 * @param nodes - The node list containing all files of the XML
	 * @return the File element
	 * @throws SystemException
	 */
	protected Element getFileById(String fileID, NodeList nodes)
	throws SystemException {
		Element file = null;
		for (int i = 0; i < nodes.getLength(); i++) {
			Element temp = (Element) nodes.item(i);
			if(temp.getAttribute("id").equals(fileID)){
				file = temp;
				break;
			}
		}
		if(file != null){
			return file;
		}else{
			throw new SystemException("The file id "+fileID+" doesn't exist.");
		}
	}

	/**
	 * Retrieve the file Element by its file path
	 * 
	 * @param filepath - The file path
	 * @param nodes - The node list containing all files of the XML
	 * @return the file Element
	 * @throws SystemException
	 */
	protected Element getFileByFilepath(String filepath, NodeList nodes)
	throws SystemException {
		Element file = null;
		for (int i = 0; i < nodes.getLength(); i++) {
			Element temp = (Element) nodes.item(i);
			if(temp.getAttribute("path").equals(filepath)){
				file = temp;
				break;
			}
		}
		if(file != null){
			return file;
		}else{
			throw new SystemException("The file "+filepath+" doesn't exist.");
		}
	}
	
	/**
	 * Retrieve the file path by its id and folder parent path
	 * 
	 * @param folderFilepath - The folder parent path
	 * @param fileId - The file id
	 * @return The file path
	 * @throws SystemException
	 */
	public String getFilepathByFileId(String folderFilepath, String fileId)
	throws SystemException {
		Document doc = getXML();
		NodeList nodes = doc.getElementsByTagName("folder");
		Element folder = getFolder(folderFilepath, nodes);
		NodeList folderNodes = folder.getElementsByTagName("file");
		Element file = getFileById(fileId, folderNodes);
		if(file != null){
			return file.getAttribute("path");
		}else{
			throw new SystemException("The file id "+fileId+" doesn't exist.");
		}
	}

	/**
	 * Initialize the XML file on the User home folder with the
	 * initial elements that are necessary to the system.
	 * 
	 * @throws SystemException
	 */
	public void init() 
	throws SystemException {
		File file = new File("users" + File.separator + "overalt_" + getUserLogin() + ".xml");

		try{
			if (!file.exists()) {
				file.createNewFile();
				FileWriter writer = new FileWriter(file);
				writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
				writer.write("<overalt>");
				writer.write("</overalt>");
				writer.flush();
				writer.close();
			}
			

		}catch (Exception e) {
			throw new SystemException("An error ocurred on system file initialization.");
		}
	}

	private String getUserLogin() {
		return Configuration.getUserInSession().getLogin();
	}

	/**
	 * Retrieve the user XML mapped on memory
	 * 
	 * @return the XML Document object
	 * @throws SystemException
	 */
	protected Document getXML() 
	throws SystemException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try{
			DocumentBuilder db = factory.newDocumentBuilder();
			File file = new File("users" + File.separator + "overalt_" + getUserLogin() + ".xml");
			if(file.exists() && file.isFile()){
				return db.parse(file);
			}else{
				throw new SystemException("An error ocurred on xml file retrieving process.");
			}
		}catch (Exception e) {
			throw new SystemException("An error ocurred on xml file retrieving process.");
		}
	}

	/**
	 * Save the XML that have been modified on memory on the XML file 
	 * @param doc - The XML Document object
	 * @throws SystemException
	 */
	protected void setXML(Document doc) 
	throws SystemException {
		File file = new File("users" + File.separator + "overalt_" + getUserLogin() + ".xml");
		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(file);
		TransformerFactory transFactory = TransformerFactory.newInstance();
		try{
			Transformer transformer = transFactory.newTransformer();
			transformer.transform(source, result);
		}catch (Exception e) {
			throw new SystemException("An error ocurred on xml file saving process.");
		}
	}
}
